﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Practices.EnterpriseLibrary.Caching;
using System.Collections;
using System.Data.SQLite;
using System.Data;

namespace EntlibCacheStorage
{
    public class SQLiteCacheDataStore : ICacheDataStore
    {
        private readonly string AddItem =
            "insert into {0}(StorageKey,key,value,RefreshAction,Expirations,ScavengingPriority,LastAccessedTime) values(@storageKey, @key, @value, @refreshAction, @expirations, @scavengingPriority, @lastAccessedTime)";
        private readonly string LoadItems =
            "select key,value,RefreshAction,Expirations,ScavengingPriority,LastAccessedTime from {0}";
        private readonly string RemoveItem = "delete from {0} where StorageKey = @storageKey";
        private readonly string GetItemCount = "SELECT COUNT(StorageKey)  from {0}";
        private readonly string Flush = "delete from {0}";

        private SQLiteHelper dbHelper;

        public SQLiteCacheDataStore()
        {
            dbHelper = new SQLiteHelper();
        }
        public void Add(string partitionName, CacheItem cacheItem)
        {
            Add(partitionName, cacheItem.Key.GetHashCode(), cacheItem.Key, SerializationUtility.ToBytes(cacheItem.Value),
                SerializationUtility.ToBytes(cacheItem.RefreshAction), SerializationUtility.ToBytes(cacheItem.GetExpirations()),
                cacheItem.ScavengingPriority, cacheItem.LastAccessedTime.Ticks);
        }

        public int Count(string partitionName)
        {
            string strSql = string.Format(GetItemCount, partitionName);
            var count = dbHelper.ExecuteScalar(strSql);
            return int.Parse(count.ToString());
        }

        public Hashtable Load(string partitionName)
        {
            DataTable dtToLoad = LoadTable(partitionName);
            Hashtable dataToReturn = new Hashtable();
            foreach (DataRow row in dtToLoad.Rows)
            {
                CacheItem cacheItem = CreateCacheItem(row);
                dataToReturn.Add(cacheItem.Key, cacheItem);
            }
            return dataToReturn;
        }

        public int Remove(string partitionName, int storageKey)
        {
            string strSql = string.Format(RemoveItem, partitionName);
            SQLiteParameter paramerer = new SQLiteParameter("@storageKey", storageKey);
            return dbHelper.ExecuteNonQuery(strSql, paramerer);
        }

        public void Truncate(string partitionName)
        {
            string strSql = string.Format(Flush, partitionName);
            dbHelper.ExecuteNonQuery(strSql);
        }

        private DataTable LoadTable(string tblName)
        {
            string strSql = string.Format(LoadItems, tblName);
            return dbHelper.ExecuteDataTable(strSql);
        }

        private void Add(string tblName, int storageKey, string key, byte[] value, byte[] refreshAction, byte[] expirations, CacheItemPriority scavengingPriority, long lastAccessedTime)
        {
            string strSql = string.Format(AddItem, tblName);
            SQLiteParameter[] parameters = new SQLiteParameter[]
            {
                new SQLiteParameter("@storageKey",storageKey),
                new SQLiteParameter("@key",key),
                new SQLiteParameter("@value",value){DbType= DbType.Binary},
                new SQLiteParameter("@refreshAction",refreshAction){DbType= DbType.Binary},
                new SQLiteParameter("@expirations",expirations){DbType= DbType.Binary},
                new SQLiteParameter("@scavengingPriority",scavengingPriority),
                new SQLiteParameter("@lastAccessedTime",lastAccessedTime)
            };
            dbHelper.ExecuteNonQuery(strSql, parameters);
        }
        
        private CacheItem CreateCacheItem(DataRow row)
        {
            string key = (string)row["key"];
            object value = DeserializeValue(row);
            CacheItemPriority scavengingPriority = (CacheItemPriority)int.Parse(row["ScavengingPriority"].ToString());
            object refreshAction = DeserializeObject(row, "RefreshAction");
            object expirations = DeserializeObject(row, "Expirations");//SerializationUtility.ToObject((byte[])row["Expirations"]);
            DateTime timestamp = ParseTime(long.Parse(row["LastAccessedTime"].ToString()));

            CacheItem cacheItem = new CacheItem((DateTime)timestamp, key, value, scavengingPriority, (ICacheItemRefreshAction)refreshAction, (ICacheItemExpiration[])expirations);
            return cacheItem;
        }

        private object DeserializeValue(DataRow row)
        {
            object value = row["value"];
            if (value == DBNull.Value)
            {
                value = null;
            }
            else
            {
                byte[] valueBytes = (byte[])value;
                value = SerializationUtility.ToObject(valueBytes);
            }
            return value;
        }

        private object DeserializeObject(DataRow data, string columnName)
        {
            object byteArrayAsObject = data[columnName];
            if (byteArrayAsObject == DBNull.Value)
            {
                return null;
            }
            else
            {
                return SerializationUtility.ToObject((byte[])byteArrayAsObject);
            }
        }

        private DateTime ParseTime(long tick)
        {
            return new DateTime(tick);
        }
    }
}
